Namespace ShapeLibrary.Design
    ''' <summary>
    ''' This is a designer for ellipses.  
    ''' </summary>
    Friend Class EllipseDesigner
        Inherits ShapeDesigner
        ' Objects we use to identify hit test locations. An ellipse
        ' is manipulated by a bounding box, which has eight sides.
        '
        Private Shared ReadOnly HitBottom As Object = New Object()
        Private Shared ReadOnly HitLeft As Object = New Object()
        Private Shared ReadOnly HitRight As Object = New Object()
        Private Shared ReadOnly HitTop As Object = New Object()
        Private Shared ReadOnly HitLowerLeft As Object = New Object()
        Private Shared ReadOnly HitLowerRight As Object = New Object()
        Private Shared ReadOnly HitUpperLeft As Object = New Object()
        Private Shared ReadOnly HitUpperRight As Object = New Object()

        '''
        '''     Implementation of the abstract property BoundingBox
        '''     from ShapeDesigner.
        '''
        Protected Overrides Property BoundingBox() As Rectangle
            Get
                Return Ellipse.Bounds
            End Get
            Set(ByVal Value As Rectangle)
                Dim bounds As PropertyDescriptor = TypeDescriptor.GetProperties(Component)("Bounds")
                If Not (bounds Is Nothing) AndAlso bounds.PropertyType Is GetType(Rectangle) Then
                    bounds.SetValue(Component, Value)
                End If
            End Set
        End Property

        '''
        '''     This is a helper property that returns the ellipse we are currently designing.
        '''     The default implementation just casts Component to an ellipse.
        '''
        Protected Overridable ReadOnly Property Ellipse() As Ellipse
            Get
                Return CType(Component, Ellipse)
            End Get
        End Property

        '''
        ''' Given a hitObject this method updates the given retangle according
        ''' to where it was hit.
        '''
        Private Function AdjustBounds(ByVal hitObject As Object, ByVal bounds As Rectangle, ByVal x As Integer, ByVal y As Integer, ByRef adjusted As Boolean) As Rectangle
            adjusted = False

            ' Adjust the X coordinate?
            '
            If hitObject Is HitLeft Or hitObject Is HitUpperLeft Or hitObject Is HitLowerLeft Or hitObject Is HitMove Then
                adjusted = True
                bounds.X += x
            End If

            ' Adjust the Y coordinate?
            '
            If hitObject Is HitTop Or hitObject Is HitUpperLeft Or hitObject Is HitUpperRight Or hitObject Is HitMove Then
                adjusted = True
                bounds.Y += y
            End If

            ' Adjust the width?
            '
            If hitObject Is HitLeft Or hitObject Is HitUpperLeft Or hitObject Is HitLowerLeft Then
                adjusted = True
                bounds.Width -= x
            End If

            If hitObject Is HitRight Or hitObject Is HitUpperRight Or hitObject Is HitLowerRight Then
                adjusted = True
                bounds.Width += x
            End If

            ' Adjust the height?
            '
            If hitObject Is HitTop Or hitObject Is HitUpperLeft Or hitObject Is HitUpperRight Then
                adjusted = True
                bounds.Height -= y
            End If

            If hitObject Is HitBottom Or hitObject Is HitLowerLeft Or hitObject Is HitLowerRight Then
                adjusted = True
                bounds.Height += y
            End If

            bounds.Width = Math.Max(bounds.Width, 1)
            bounds.Height = Math.Max(bounds.Height, 1)

            Return bounds
        End Function

        '''
        '''     Implementation for the abstract method GetHitTest
        '''     from ShapeDesigner.  We support eight possible 
        '''     hit test locations for an ellipse:  any corner,
        '''     the midpoints of the bounding box, and on the
        '''     ellipse itself.
        '''
        Friend Overrides Function GetHitTest(ByVal x As Integer, ByVal y As Integer) As Object
            ' Is the point within the bounding rectangle for this ellipse?
            '
            Dim bounds As Rectangle = BoundingBox
            bounds.Inflate(AdornmentDimensions)

            If Not bounds.Contains(x, y) Then
                Return Nothing
            End If

            bounds = BoundingBox

            ' Check to see if the point is within one of our adornments.
            '
            Dim adornmentOffset As Integer = AdornmentDimensions.Width / 2
            Dim adornmentRect As Rectangle = New Rectangle(bounds.X - adornmentOffset, bounds.Y - adornmentOffset, AdornmentDimensions.Width, AdornmentDimensions.Height)

            If adornmentRect.Contains(x, y) Then
                Return HitUpperLeft
            End If

            adornmentRect.Y = bounds.Bottom - adornmentOffset
            If adornmentRect.Contains(x, y) Then
                Return HitLowerLeft
            End If

            adornmentRect.X = bounds.Right - adornmentOffset
            If adornmentRect.Contains(x, y) Then
                Return HitLowerRight
            End If

            adornmentRect.Y = bounds.Top - adornmentOffset
            If adornmentRect.Contains(x, y) Then
                Return HitUpperRight
            End If

            adornmentRect.X = bounds.Left + bounds.Width / 2 - adornmentOffset
            If adornmentRect.Contains(x, y) Then
                Return HitTop
            End If

            adornmentRect.Y = bounds.Bottom - adornmentOffset
            If adornmentRect.Contains(x, y) Then
                Return HitBottom
            End If

            adornmentRect.X = bounds.Left - adornmentOffset
            adornmentRect.Y = bounds.Top + bounds.Height / 2 - adornmentOffset
            If adornmentRect.Contains(x, y) Then
                Return HitLeft
            End If

            adornmentRect.X = bounds.Right - adornmentOffset
            If adornmentRect.Contains(x, y) Then
                Return HitRight
            End If

            Return HitMove
        End Function

        '''
        '''     Implementation for the abstract method GetCursor
        '''     from ShapeDesigner.  We return one of five cursors,
        '''     depending on the value of the hitObject passed in.
        '''     If the object is HitMove, we return the size all
        '''     cursor which is a four headed arrow (indicating a move
        '''     of the shape).  For other hit tests we return a
        '''     cursor that matches the direction of the movement.
        '''
        Friend Overrides Function GetCursor(ByVal hitObject As Object) As Cursor
            If hitObject Is HitLeft Or hitObject Is HitRight Then
                Return Cursors.SizeWE
            End If

            If hitObject Is HitTop Or hitObject Is HitBottom Then
                Return Cursors.SizeNS
            End If

            If hitObject Is HitUpperLeft Or hitObject Is HitLowerRight Then
                Return Cursors.SizeNWSE
            End If

            If hitObject Is HitUpperRight Or hitObject Is HitLowerLeft Then
                Return Cursors.SizeNESW
            End If

            Return Cursors.SizeAll
        End Function

        '''
        '''     Implementation for the abstract method Drag
        '''     from ShapeDesigner.  Here we either move one
        '''     or both points of the line depending on what
        '''     hit object was passed.
        '''
        Friend Overrides Sub Drag(ByVal hitObject As Object, ByVal x As Integer, ByVal y As Integer)
            ' Whenever we manipulate more than one property at a time
            ' we create a transaction.  A single transaction is a single
            ' unit of work, and this is what is undone. The names for the
            ' transaction below will be displayed in the undo menu's
            ' drop down list of undoable actions.
            '
            Dim host As IDesignerHost = CType(GetService(GetType(IDesignerHost)), IDesignerHost)

            If Not (host Is Nothing) Then
                Dim prop As PropertyDescriptor = TypeDescriptor.GetProperties(Component)("Bounds")
                Dim bounds As Rectangle = CType(prop.GetValue(Component), Rectangle)

                Dim adjusted As Boolean
                bounds = AdjustBounds(hitObject, bounds, x, y, adjusted)

                If adjusted Then
                    Dim trans As DesignerTransaction = Nothing

                    If hitObject Is HitMove Then
                        trans = host.CreateTransaction("Move " + Component.Site.Name)
                    Else
                        trans = host.CreateTransaction("Resize " + Component.Site.Name)
                    End If

                    Try
                        prop.SetValue(Component, bounds)
                        trans.Commit()
                    Finally
                        CType(trans, IDisposable).Dispose()
                    End Try
                End If
            End If
        End Sub

        '''
        '''     Implementation for the abstract method DrawAdornments 
        '''     from ShapeDesigner.  For a line, we draw grab handles
        '''     on each end of the line.
        '''
        Friend Overrides Sub DrawAdornments(ByVal g As Graphics, ByVal primary As Boolean)
            ' We draw eight adornments around the rectangle.
            '
            Dim adornmentOffset As Integer = AdornmentDimensions.Width / 2
            Dim bounds As Rectangle = BoundingBox
            Dim adornmentRect As Rectangle = New Rectangle(bounds.X - adornmentOffset, bounds.Y - adornmentOffset, AdornmentDimensions.Width, AdornmentDimensions.Height)

            ControlPaint.DrawGrabHandle(g, adornmentRect, primary, True)

            adornmentRect.X = bounds.X + bounds.Width / 2 - adornmentOffset
            ControlPaint.DrawGrabHandle(g, adornmentRect, primary, True)

            adornmentRect.X = bounds.Right - adornmentOffset
            ControlPaint.DrawGrabHandle(g, adornmentRect, primary, True)

            adornmentRect.Y = bounds.Y + bounds.Height / 2 - adornmentOffset
            ControlPaint.DrawGrabHandle(g, adornmentRect, primary, True)

            adornmentRect.Y = bounds.Bottom - adornmentOffset
            ControlPaint.DrawGrabHandle(g, adornmentRect, primary, True)

            adornmentRect.X = bounds.X + bounds.Width / 2 - adornmentOffset
            ControlPaint.DrawGrabHandle(g, adornmentRect, primary, True)

            adornmentRect.X = bounds.X - adornmentOffset
            ControlPaint.DrawGrabHandle(g, adornmentRect, primary, True)

            adornmentRect.Y = bounds.Y + bounds.Height / 2 - adornmentOffset
            ControlPaint.DrawGrabHandle(g, adornmentRect, primary, True)
        End Sub

        '''
        '''     Implementation for the abstract method DrawDragFeedback
        '''     from ShapeDesigner.  This draws reversible feedback by
        '''     drawing a reversible line.
        '''
        Friend Overrides Sub DrawDragFeedback(ByVal hitObject As Object, ByVal backColor As Color, ByVal screenOffset As Point, ByVal mouseOffset As Point)
            Dim adjusted As Boolean
            Dim bounds As Rectangle = AdjustBounds(hitObject, BoundingBox, mouseOffset.X, mouseOffset.Y, adjusted)
            bounds.Offset(screenOffset)

            If adjusted Then
                Dim startPoint As Point = New Point(bounds.X, bounds.Y)
                Dim endPoint As Point = New Point(bounds.Right, bounds.Y)

                ControlPaint.DrawReversibleLine(startPoint, endPoint, backColor)

                startPoint = endPoint
                endPoint.Y = bounds.Bottom
                ControlPaint.DrawReversibleLine(startPoint, endPoint, backColor)

                startPoint = endPoint
                endPoint.X = bounds.X
                ControlPaint.DrawReversibleLine(startPoint, endPoint, backColor)

                startPoint = endPoint
                endPoint.Y = bounds.Y
                ControlPaint.DrawReversibleLine(startPoint, endPoint, backColor)
            End If
        End Sub
    End Class
End Namespace
